/*
 * Copyright (c) 2005 - 2011 CAS Dev Team
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 4. Neither the name of the CAS Dev. Team nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *      ASSQLConnectorClass.cpp
 *
 * $CAS$
 */

#include <ASSQLConnectorObject.hpp>

#include <ASException.hpp>

#include <SQLCredentialsParser.hpp>
#include <SQLayer.hpp>
#include <SQLayerAF.hpp>
#include <SQLayerConnStats.hpp>
#include <SQLayerException.hpp>

#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#ifdef __linux__
#include <time.h>
#endif

namespace CAS_MOD
{

ASSQLConnectorObject::ConnectionPair::ConnectionPair(SQL::ConnectionBase  * pDriver,
                                                     SQL::Connection      * pConnection): driver(pDriver),
                                                                                          connection(pConnection),
                                                                                          ts(0.0)
{
	;;
}

// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Class ASSQLConnectorObject
//

//
// Constructor
//
ASSQLConnectorObject::ASSQLConnectorObject(const UINT_32 iIErrorTimeout): iErrorTimeout(iIErrorTimeout)
{
	using namespace SQL;
#ifdef __linux__
	srandom(time(0));
#else
	srandomdev();
#endif

	SQLayerAF::InitLibrary();
}

//
// Add database server
//
INT_32 ASSQLConnectorObject::AddConnection(const STLW::string  & sDriver,
                                           const STLW::string  & sCredentials)
{
	using namespace SQL;
//fprintf(stderr, "AddConnection: driver = %s, connection = %s\n", sDriver.c_str(), sCredentials.c_str());
	ConnectionBase  * pConnectionBase = NULL;
	try
	{
		pConnectionBase = SQLayerAF::CreateDriver(sDriver.c_str());
	}
	catch(...)
	{
		if (pConnectionBase != NULL) { SQLayerAF::DestroyDriver(pConnectionBase); }

		throw;
	}

	// Check driver
	if (pConnectionBase == NULL)
	{
		STLW::string sTMP("Unsupported driver: \"");
		sTMP += sDriver + "\"";
		throw LogicError(sTMP.c_str());
	}

	Connection * pConnection = NULL;
	try
	{
		pConnection = new Connection(pConnectionBase);
		if (pConnection -> SetCredentials(sCredentials.c_str()) == -1)
		{
//fprintf(stderr, "ERROR: in SetCredentials %s\n", pConnection -> GetError());
			throw LogicError(pConnection -> GetError());
		}
	}
	catch(...)
	{
//fprintf(stderr, "ERROR: in new Connection/SetCredentials\n");
		if (pConnection != NULL) { delete pConnection; }

		throw;
	}
//fprintf(stderr, "OK: in new Connection/SetCredentials\n");

	// Create new
	vConnections.push_back(ConnectionPair(pConnectionBase, pConnection));

return 0;
}

//
// Get pointer to the permanent connection
//
SQL::Connection * ASSQLConnectorObject::GetPermConnection()
{
	return GetServer();
}

//
// Get pointer to the session connection
//
SQL::Connection * ASSQLConnectorObject::GetConnection()
{
	SQL::Connection * pConnection = GetServer();

	vRequestConnections.push_back(pConnection);

return pConnection;
}

//
// Get connection to sql server
//
SQL::Connection * ASSQLConnectorObject::GetServer()
{
	using namespace SQL;

	const UINT_32 iElements = vConnections.size();
	if (iElements == 0) { throw LogicError("No available connections in pool"); }
	if (iElements == 1)
	{
		Connection * pConnection = vConnections[0].connection;
		if (pConnection == NULL) { throw LogicError("Connection IS NULL Pointer Object (this should *NOT* happened)"); }

		// Check connection
		if (pConnection -> Ping() != -1) { return pConnection; }
		// Connect, if need
		if (pConnection -> Connect() != -1) { return pConnection; }

		// Can't connect to server, return object to connection to check errors
		return pConnection;
	}

	// We need to compute dispersion and check 3-s criteria
	W_FLOAT dConnAverage  = 0.0;
	W_FLOAT dQueryAverage = 0.0;
	UINT_32 iPos = 0;
	for (; iPos < iElements; ++iPos)
	{
		Connection * pConnection = vConnections[iPos].connection;
		if (pConnection == NULL) { throw LogicError("Connection is NULL Pointer Object (this should *NOT* happened)"); }

		const ConnectionStats & oStats = pConnection -> GetStats();
		dConnAverage  += oStats.conn_average_time;
		dQueryAverage += oStats.query_average_time;
//fprintf(stderr, "(%f, %f) ", oStats.conn_average_time, oStats.query_average_time);
	}
//fprintf(stderr, "\n");

	dConnAverage  /= iElements;
	dQueryAverage /= iElements;

	W_FLOAT dConnDispersion  = 0.0;
	W_FLOAT dQueryDispersion = 0.0;
	iPos = 0;
	for (; iPos < iElements; ++iPos)
	{
		Connection * pConnection = vConnections[iPos].connection;
		const ConnectionStats & oStats = pConnection -> GetStats();

		W_FLOAT dTMP = oStats.conn_average_time - dConnAverage;
		dConnDispersion  += dTMP * dTMP;

		dTMP = oStats.query_average_time - dQueryAverage;
		dQueryDispersion += dTMP * dTMP;
	}
	dConnDispersion  = C_SIGMA_CRITERIA * sqrt(dConnDispersion / iElements);
	dQueryDispersion = C_SIGMA_CRITERIA * sqrt(dQueryDispersion / iElements);

	oConnectorStatistics.conn_avg_time    = dConnAverage;
	oConnectorStatistics.conn_dispersion  = dConnDispersion;
	oConnectorStatistics.query_avg_time   = dQueryAverage;
	oConnectorStatistics.query_dispersion = dQueryDispersion;

	// Get random connection
	const UINT_32 iStartPos = random() % iElements;
//fprintf(stderr, "connection (%f / %f), query (%f / %f) server: %d\n", dConnAverage, dConnDispersion, dQueryAverage, dQueryDispersion, iStartPos);

	// iStartPos .. iElements
	Connection * pConnection = GetConnnectionFromRange(iStartPos, iElements, true);
	if (pConnection != NULL) { return pConnection; }
	// 0 .. iStartPos
	pConnection = GetConnnectionFromRange(0, iStartPos, true);
	if (pConnection != NULL) { return pConnection; }

fprintf(stderr, "Statistics failed, try to get first available connection!\n");

	// Try to get ANY sonnection, without using statistics
	// iStartPos .. iElements
	pConnection = GetConnnectionFromRange(iStartPos, iElements, false);
	if (pConnection != NULL) { return pConnection; }
	// 0 .. iStartPos
	pConnection = GetConnnectionFromRange(0, iStartPos, false);
	if (pConnection != NULL) { return pConnection; }

fprintf(stderr, "Everything failed!\n");

// Can't connect to server, return object to iStartPos connection to check errors (last chance!)
return vConnections[iStartPos].connection;
}

//
// Get connection to sql server
//
SQL::Connection * ASSQLConnectorObject::GetConnnectionFromRange(const UINT_32  iStartPos,
                                                                const UINT_32  iEndPos,
                                                                const bool     bUseStatistics)
{
	using namespace SQL;

	for (UINT_32 iPos = iStartPos; iPos < iEndPos; ++iPos)
	{
		ConnectionPair & oConnectionPair = vConnections[iPos];

		Connection * pConnection = oConnectionPair.connection;
		const ConnectionStats & oStats = oConnectionPair.connection -> GetStats();

		// Get current time
		struct timespec oCurrentTS;
		clock_gettime(CLOCK_REALTIME, &oCurrentTS);
//		gettimeofday(&oCurrentTS, NULL);
		const W_FLOAT dCurrentTS = oCurrentTS.tv_sec + 1.0E-9 * oCurrentTS.tv_nsec;

		// Check stats
		if (bUseStatistics &
		    (oStats.conn_average_time  > oConnectorStatistics.conn_avg_time + oConnectorStatistics.conn_dispersion ||
		     oStats.query_average_time > oConnectorStatistics.query_avg_time + oConnectorStatistics.query_dispersion)
		   )
		{
			// Check timeout
			if (oConnectionPair.ts + iErrorTimeout > dCurrentTS)
			{
fprintf(stderr, "SKIPPED %d (%f %f) (%f %f)\n ", iPos, oStats.conn_average_time,  oConnectorStatistics.conn_avg_time  + oConnectorStatistics.conn_dispersion,
                                                       oStats.query_average_time, oConnectorStatistics.query_avg_time + oConnectorStatistics.query_dispersion);
				continue;
			}
//fprintf(stderr, "FROM PREV SKIPPED %d\n", iPos);
			// Store timestamp
			oConnectionPair.ts = dCurrentTS;
		}

		// Check connection
		if (pConnection -> Ping() != -1) { return pConnection; }
		// Connect, if need
		if (pConnection -> Connect() != -1) { return pConnection; }

		// Store timestamp
		oConnectionPair.ts = dCurrentTS;
fprintf(stderr, "FAILED %d\n", iPos);
	}
return NULL;
}

//
// Get connection statistics
//
const ASSQLConnectorObject::ConnectorStatistics & ASSQLConnectorObject::GetStatistics() const { return oConnectorStatistics; }

//
// Get object type
//
CCHAR_P ASSQLConnectorObject::GetObjectType() const { return "ASObject"; }

//
// Get object name
//
CCHAR_P ASSQLConnectorObject::GetObjectName() const { return "SQLConnector"; }

//
// Handle about event
//
INT_32 ASSQLConnectorObject::HandleEvent(const UINT_32 iEventId)
{
	// Clear garbage
	if (iEventId == C_START_OF_REQUEST) { STLW::vector<SQL::Connection *> vTMP; vRequestConnections.swap(vTMP); }
	// Disconnect connection
	else if (iEventId == C_END_OF_REQUEST)
	{
		STLW::vector<SQL::Connection *>::iterator itvRequestConnections = vRequestConnections.begin();
		while(itvRequestConnections != vRequestConnections.end())
		{
			(*itvRequestConnections) -> Disconnect();
			++itvRequestConnections;
		}

		// Clear garbage
		STLW::vector<SQL::Connection *> vTMP; vRequestConnections.swap(vTMP);
	}

return 0;
}

//
// A destructor
//
ASSQLConnectorObject::~ASSQLConnectorObject() throw()
{
	using namespace SQL;

	STLW::vector<ConnectionPair>::iterator itvConnections = vConnections.begin();
	while(itvConnections != vConnections.end())
	{
		delete itvConnections -> connection;
		SQLayerAF::DestroyDriver(itvConnections -> driver);
		++itvConnections;
	}
	SQLayerAF::FinishLibrary();
}

} // namespace CAS_MOD
// End.

